Next.js에서 환경변수 다루기
React.js와 Next.js 모두 환경변수를 사용할 수 있습니다. 본 글에서는 Next.js 개발 환경을 전제로 설명하겠습니다. React.js도 동일하게 진행하시면 됩니다.
기존 문제점
Next.js에서 환경변수를 사용할 때 다음과 같은 문제가 발생합니다.
- 정확한 타입 추론이 어려움
- 불필요한 코드 추가 필요
대표적으로 위 2가지의 문제가 있습니다. 하나씩 알아보도록 하겠습니다.
타입 추론 불가 및 불필요한 처리
// string | undefined
const key = process.env.key;
위와 같이 사용할 경우 process.env.key
는 string | undefined
타입이 됩니다.
개발자가 해당 값이 항상 존재한다고 확신하더라도, 타입 상 undefined
를 제거해주어야 합니다.
그래서 다음과 같은 불필요한 처리를 하게 됩니다.
const key1 = process.env.key as string;
const key2 = process.env.key || "";
as string
이나 || ""
는 불필요할 뿐 아니라 타입 안전성도 떨어뜨릴 수 있습니다.
유니언 타입이지만 추론 불가
type Country = "Korean" | "Japan" | "China";
const country = process.env.country;
country
값이 반드시 "Korea" | "Japan" | "China"
중 하나여야 한다고 해도, 타입스크립트는 이를 추론하지 못합니다.
const country1 = process.env.country as Country;
const country2 = process.env.country;
if (isCountry(country2)) {
// do something ...
}
이처럼 커스텀 타입 가드나 타입 단언을 사용해야 해서 코드가 복잡해지고 유지보수가 어려워집니다.
해결방법
빌드타임 + 런타임 유효성 검사를 통해 환경변수를 안전하게 관리할 수 있습니다.
이를 위해 타입스크립트와 잘 어울리는 zod
라이브러리를 사용하였습니다.
해당 라이브러리를 설치해주셔야 진행 가능합니다.
환경 변수 조건 예시
다음과 같은 조건이 있다고 가정해보겠습니다.
key
: 빈 문자열이 아닌 문자열api
: URL 형식의 문자열count
: 숫자country
:"Korea", "Japan", "China"
중 하나
zod
스키마 정의
환경변수에 대한 스키마를 구현해주어야 합니다.
스키마는 데이터 구조라고 생각해주시면 됩니다.
import { z } from "zod";
const schema = z.object({
key: z.string().nonempty("key 값은 필수 값입니다."),
api: z
.string()
.nonempty("api 값은 필수 값입니다.")
.url()
.refine((val: string) => /\.[a-z]+$/.test(new URL(val).hostname), {
message: "api 값이 유효한 도메인이 아님",
}),
count: z.number(),
country: z.enum(["Korea", "Japan", "China"] as const, {
errorMap: () => ({ message: "country 값이 유효하지 않음" }),
}),
});
위 코드를 확인해보면 추가적인 설명 없이 충분히 이해할 수 있습니다. 이처럼 zod
를 이용하면 객체의 필드에 대한 유효성 조건을 명확하게 설정할 수 있고 관리도 편하며 가독성도 높아집니다.
환경변수 객체
const envObject = {
key: process.env.NEXT_PUBLIC_KEY,
api: process.env.NEXT_PUBLIC_API,
count: Number(process.env.NEXT_PUBLIC_COUNT),
country: process.env.NEXT_PUBLIC_COUNTRY,
};
유효성 검사를 하기위한 환경변수 객체를 생성해주어야 합니다.
유효성 검사 및 타입 완성된 환경변수 반환
const validEnv = () => {
const result = schema.safeParse(envObject);
if (!result.success) {
console.error(result.error.format());
throw new Error("Error");
} else {
return result.data;
}
};
const env = validEnv();
성공 시 env
객체는 정확한 타입이 지정된 상태로 사용 가능합니다. 따라서 자동완성도 잘 동작합니다.

만약 잘못된 값을 넣으면 어떻게 될까요? 나머지 값은 잘 들어갔고 아래 2개의 값이 잘못 들어갔다고 가정해보겠습니다.
NEXT_PUBLIC_KEY=""
NEXT_PUBLIC_COUNTRY="Korea2"
다음과 같은 에러 메시지가 발생하게 됩니다.
{
key: { _errors: [ 'key 값은 필수입니다.' ] },
country: { _errors: [ 'country 값이 유효하지 않습니다.' ] }
}
따라서 위 코드를 빌드타임에서 유효성 검사를 진행하여 환경변수에 잘못된 값이 있는 지 확인하는 것이 중요합니다.
전체 코드
import { z } from "zod";
const schema = z.object({
key: z.string().nonempty("key 값은 필수 값입니다."),
api: z
.string()
.nonempty("api 값은 필수 값입니다.")
.url()
.refine((val: string) => /\.[a-z]+$/.test(new URL(val).hostname), {
message: "api 값이 유효한 도메인이 아님",
}),
count: z.number(),
country: z.enum(["Korea", "Japan", "China"] as const, {
errorMap: () => ({ message: "country 값이 유효하지 않음" }),
}),
});
const envObject = {
key: process.env.NEXT_PUBLIC_KEY,
api: process.env.NEXT_PUBLIC_API,
count: process.env.NEXT_PUBLIC_COUNT,
country: process.env.NEXT_PUBLIC_COUNTRY,
};
const validEnv = () => {
const result = schema.safeParse(envObject);
if (!result.success) {
console.error(result.error.format());
throw new Error("Error");
} else {
return result.data;
}
};
const env = validEnv();
결론
이번 게시글에서는 환경변수 유효성 검사와 타입 설정에 대해 알아보았습니다. 빌드타임, 런타임 각각 실행하게 되면 사전에 잘못된 데이터를 막을 수 있고 타입도 자동완성이 되기 때문에 개발 효율성이 증가하게 될 것입니다.